
import { isEqual, uniqueId, cloneDeep } from 'lodash';
import * as React from 'react';
import loadjs from 'loadjs';
import classnames from 'classnames';
import { RightMenu, RightMenuStore } from './rightMenu';

import './index.less';
const getLength = (str) => {
    let realLength = 0, len = str.length, charCode = -1;
    for (let i = 0; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode >= 0 && charCode <= 128) realLength += 1;
        else realLength += 2;
    }
    return realLength;
};
const getSubStr = (str, start, length) => {
    let rt = [], currentLength = 0, len = str.length, charCode = -1;
    for (let i = 0; i < len; i++) {
        charCode = str.charCodeAt(i);
        if (charCode >= 0 && charCode <= 128) {
            currentLength++;
        } else {
            currentLength += 2;
        }
        if (currentLength > start && currentLength <= length) {
            rt.push(str.charAt(i));
        }
    }
    return rt.join('');
}
const getShortStr = (ele, data, w = 100) => {
    const length = parseInt(w / 6);
    const datalength = data ? getLength(data) : 0;
    if (length >= datalength) {
        return data;
    }
    return getSubStr(data, 0, length - 3) + '...';
}
export default class D3Tree extends React.Component {
    totalNodes = 0;
    maxLabelLength = 0;
    i = 0;
    selectedPath = [];
    currentPath = [];
    overedPath = [];
    static defaultProps = {
        widthToggle: true,
        showRight: true,
        layout: '0',
        showMenu: false
    }
    constructor(props) {
        super(props);
        this.state = {
            duration: 750,
            hoverNode: undefined,
            id: `d3TreeContainer_${new Date().getTime()}_${uniqueId()}`,
            width: 160,
            height: 60
        }
        this.didMountTree = false;
        this.rightMenuStore = new RightMenuStore();
    }
    render() {
        const { showRight, style, showMenu } = this.props;
        return (
            <div className={classnames("d3-tree-wrapper", { "with-right": showRight })}>
                <div
                    ref={ref => this.container = ref}
                    id={this.state.id}
                    className="d3-tree"
                    style={style}
                    onContextMenu={showMenu ? this.onContextMenu : undefined}
                />
                <div className="right">
                    {this.props.showRight && this.renderRight()}
                </div>
                <div className="border-selection" ref={ref => this.selection = ref}></div>
            </div>
        )
    }
    onContextMenu = (e) => {
        if (this.isClickNode && this.opreateNode) {
            const left = e.clientX;
            const top = e.clientY;
            this.showRightMenu();
            this.rightMenuStore.show(left, top,
                this.opreateNode.id === this.root.id,
                this.getHasChild(this.opreateNode),
                this.getChildVisible(this.opreateNode));
        }
        this.isClickNode = false;
        e.stopPropagation && e.stopPropagation();
        e.preventDefault && e.preventDefault();
        e.nativeEvent && e.nativeEvent.preventDefault();
    }
    showRightMenu = () => {
        if (!this.menuWrapper) {
            this.menuWrapper = document.createElement('div');
            document.body.appendChild(this.menuWrapper);
        }
        ReactDOM.render(<RightMenu
            data={this.opreateNode}
            store={this.rightMenuStore}
            onToggleChildren={this.toggleChildren}
            onAddChild={this.props.onAddChild}
            onDeleteChild={this.props.onDeleteChild}
            onDelete={this.props.onDelete}
            onReject={this.props.onReject}
            onSetting={this.props.onSetting}
            onBatchReject={this.props.onBatchReject}
            onSynchro={this.props.onSynchro}
        />, this.menuWrapper);
    }
    clickAnyWhere = (e) => {
        this.rightMenuStore.hide();
        if (this.startMove && this.selection) {
            this.startMove = false;
            this.selection.style.display = 'none';
            const top = parseInt(this.selection.style.top);
            const left = parseInt(this.selection.style.left);
            const width = parseInt(this.selection.style.width);
            const height = parseInt(this.selection.style.height);

            this.selection.style.height = '0px';
            this.selection.style.width = '0px';
        }
    }
    mouseup = (d) => {
        const e = window.event;
        if (e.button === 2) {
            this.opreateNode = d;
            this.isClickNode = true;
            this.click(d);
        }

    }
    renderRight = () => {
        if (typeof this.props.renderRight === 'function') {
            if (this.selectedNode && this.props.renderRight) {
                return this.props.renderRight(this.selectedNode);
            } else {
                return null;
            }
        } else {
            return this.props.renderRight;
        }
    }
    diableMouseOut = () => {
        this.canTriggerMouseOut = false;
    }
    enableMouseOut = () => {
        this.canTriggerMouseOut = true;
        this.outNode();
    }
    componentWillReceiveProps(nextProps) {
        if (!isEqual(this.props.data, nextProps.data) ||
            this.props.layout !== nextProps.layout && this.container &&
            (this.didMountTree = false, this.container.innerHTML = "", true)) {
            if (nextProps.data) {
                clearTimeout(this.initTimer);
                this.initTimer = setTimeout(() => {
                    this.initTree(nextProps.data);
                }, 100);
            }
        }
        if (this.props.scale !== nextProps.scale) {
            if (this.svgGroup) {
                const transform = this.svgGroup.attr("transform");
                if (transform) {
                    const scale = nextProps.scale / 10;
                    this.svgGroup.attr("transform", `${transform.split('scale')[0]}scale(${scale})`);
                }
            }
        }
    }
    componentWillUnmount() {
        window.removeEventListener('resize', this.resize);
        document.removeEventListener('mouseup', this.clickAnyWhere);
        if (this.container) {
            /*   this.container.removeEventListener('mousedown', this.onMouseDown);
              this.container.removeEventListener('mousemove', this.onMouseMove); */
        }
        if (this.menuWrapper) {
            ReactDOM.unmountComponentAtNode(this.menuWrapper);
            document.body.removeChild(this.menuWrapper);
            delete this.menuWrapper;
        }
    }
    componentDidMount() {
        window.addEventListener('resize', this.resize);
        document.addEventListener('mouseup', this.clickAnyWhere);
        if (this.container) {
            /*  this.container.addEventListener('mousedown', this.onMouseDown);
             this.container.addEventListener('mousemove', this.onMouseMove); */
        }
        if (!loadjs.isDefined('weaEdcD3js')) {
            loadjs(['/edc/d3/d3.v3.js'], 'weaEdcD3js', {
                success: () => {
                    if (this.container && this.props.data) {
                        this.initTree(this.props.data);
                        this.resize();
                        this.click(this.root);
                    }
                }
            });
        } else {
            if (this.container && this.props.data) {
                this.initTree(this.props.data);
                this.resize();
                this.click(this.root);
            }
        }

    }
    onMouseDown = (e) => {
        if (e.button === 0 && this.selection && e.target.tagName === 'svg') {
            this.selectionLeft = e.clientX;
            this.selectionTop = e.clientY;
            this.selection.style.display = 'block';
            this.selection.style.top = this.selectionTop + 'px';
            this.selection.style.left = this.selectionLeft + 'px';
            this.startMove = true;
        }
    }
    onMouseMove = (e) => {
        if (this.startMove) {
            this.selection.style.height = (e.y - this.selectionTop) + 'px';
            this.selection.style.width = (e.x - this.selectionLeft) + 'px';
            e.stopPropagation && e.stopPropagation();
            e.preventDefault && e.preventDefault();
            e.nativeEvent && e.nativeEvent.preventDefault();
        }
    }
    resize = () => {
        clearTimeout(this.resizeTimer);
        this.resizeTimer = setTimeout(() => {
            const viewerHeight = this.container.clientHeight;
            const viewerWidth = this.container.clientWidth;
            d3.select(`#${this.state.id}>svg`)
                .attr("width", viewerWidth)
                .attr("height", viewerHeight);
        }, 100);
    }
    getSize = () => {
        const { layout } = this.props;
        const { width, height } = this.state;
        if (layout === '1') { // 上
            return {
                nodeSize: [width + 10, height],
                width,
                height,
                deep: width
            }
        } else if (layout === '3') { // 下
            return {
                nodeSize: [width + 10, height],
                width,
                height,
                deep: width * -1
            }
        } else if (layout === '0') { // 左
            return {
                nodeSize: [height + 10, width],
                width,
                height,
                deep: width * 2,
            }
        } else if (layout === '2') { // 右
            return {
                nodeSize: [height + 10, width],
                width,
                height,
                deep: width * -2,
            }
        }
    }
    searchNode = (name, nodes = [this.root]) => {
        const searchedNode = [];
        nodes.forEach(node => {
            if (node.name.toLowerCase().indexOf(name) > -1) {
                searchedNode.push(node);
            }
            if (node.children) {
                searchedNode.push(...this.searchNode(name, node.children));
            }
            if (node._children) {
                searchedNode.push(...this.searchNode(name, node._children));
            }
        });
        return searchedNode;
    }
    initTree(treeData) {
        const { layout } = this.props;
        const { width, height } = this.state;
        const { nodeSize } = this.getSize();
        if (this.container && window.d3) {
            const viewerHeight = this.container.clientHeight;
            const viewerWidth = this.container.clientWidth;
            this.tree = d3.layout.tree().nodeSize(nodeSize);
            // define a d3 diagonal projection for use by the node paths later on.
            this.diagonal = d3.svg.diagonal()
                .projection((d) => {
                    return (layout === '1' || layout === '3') ?
                        [d.x + width / 2, d.y + height / 2] :
                        [d.y + width / 2, d.x + height / 2];
                });

            this.sortTree();
            if (!this.didMountTree) {
                const zoom = (e) => {
                    this.svgGroup.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
                    if (this.props.zoom) {
                        this.props.zoom(d3.event.scale)
                    }
                }
                if (this.props.zoom) {
                    this.props.zoom(1)
                }
                this.zoomListener = d3.behavior.zoom().scaleExtent([0.1, 2]).on("zoom", zoom);

                // define the baseSvg, attaching a class for styling and the zoomListener
                this.baseSvg = d3.select("#" + this.state.id).append("svg")
                    .attr("width", viewerWidth)
                    .attr("height", viewerHeight)
                    .attr("class", "overlay")
                    .call(this.zoomListener);
                this.svgGroup = this.baseSvg.append("g");
            }
            // Define the drag listeners for drag/drop behaviour of nodes.
            // Define the root
            const { x0 = viewerHeight / 2, y0 = 0 } = this.root || {};
            this.root = cloneDeep(treeData)
            this.root.x0 = x0
            this.root.y0 = y0;

            // Layout the tree initially and center on the root node.
            this.update(this.root);
            if (!this.didMountTree) {
                this.centerNode(this.root);
            }
            //选中的节点在不在树中时 选中根节点
            const curNodeId = this.selectedNode.id;
            const treeNodeIds = [treeData.id];
            treeData.children.map(c => {
                treeNodeIds.push(c.id);
            })
            if (treeNodeIds.indexOf(curNodeId) < 0) {
                this.click(this.root);
            }
            this.didMountTree = true;
        } else {
            setTimeout(() => {
                this.initTree(treeData);
            }, 50);
        }
    }

    centerNode = (source) => {
        const { layout } = this.props;
        const { width, height } = this.state;
        const viewerHeight = this.container.clientHeight;
        const viewerWidth = this.container.clientWidth;
        const scale = this.zoomListener.scale();
        const isRoot = source === this.root;
        this.selectedNode = source;
        let x = 0;
        let y = 0;
        if (layout === '1') {
            y = -source.y0;
            x = -source.x0;
            x = x * scale + viewerWidth / 2;
            if (isRoot) {
                y = y * scale + height;
            } else {
                y = y * scale + viewerHeight / 2;
            }
        } else if (layout === '3') {
            y = -source.y0;
            x = -source.x0;
            x = x * scale + viewerWidth / 2;
            if (isRoot) {
                y = y * scale + viewerHeight - height * 2;
            } else {
                y = y * scale + viewerHeight / 2;
            }
        } else if (layout === '2') {
            x = -source.y0;
            y = -source.x0;
            if (isRoot) {
                x = x * scale + viewerWidth - width * 2;
            } else {
                x = x * scale + viewerWidth / 2;
            }
            y = y * scale + viewerHeight / 2;
        } else {
            x = -source.y0;
            y = -source.x0;
            if (isRoot) {
                x = x * scale + width;
            } else {
                x = x * scale + viewerWidth / 2;
            }
            y = y * scale + viewerHeight / 2;
        }
        this.svgGroup.transition()
            .duration(this.state.duration)
            .attr("transform", "translate(" + x + "," + y + ")scale(" + scale + ")");
        this.zoomListener.scale(scale);
        this.zoomListener.translate([x, y]);
        this.click(source);
    }
    collapse = (d) => {
        if (d.children) {
            d._children = d.children;
            d._children.forEach(collapse);
            d.children = null;
        }
    }

    expand = (d) => {
        if (d._children) {
            d.children = d._children;
            d.children.forEach(expand);
            d._children = null;
        }
    }
    sortTree = () => {
        this.tree.sort((a, b) => {
            return parseInt(a.id) - parseInt(b.id);
        });
    }
    getXY = (x, y) => {
        const { layout } = this.props;
        if (layout === '1' || layout === '3') { // 上下
            return `${x},${y}`;
        } else {
            return `${y},${x}`;
        }
    }

    update = (source) => {
        if (!this.tree) {
            setTimeout(() => {
                this.update(source);
            }, 50);
            return;
        }
        const { desc, showname, showSubmitCount = true, nameTitle } = this.props;
        const { duration } = this.state;
        const { deep, width, height, textAnchor } = this.getSize();
        // Compute the new tree layout.
        window.tree = this.tree;
        const nodes = this.tree.nodes(this.root).reverse();
        const links = this.tree.links(nodes);
        const isShownameFunc = typeof showname === 'function';
        const isDescFunc = typeof desc === 'function';
        // Normalize for fixed-depth.
        nodes.forEach((d) => {
            d.y = d.depth * deep;
            if (d.current) {
                this.currentPath = this.getLinksToParents(d);
            }
        });

        // Update the nodes…
        const node = this.svgGroup.selectAll("g.node")
            .data(nodes, (d) => d.id);

        // Enter any new nodes at the parent's previous position.
        const nodeEnter = node.enter().append("g")
            .attr("class", 'node')
            .attr("transform", d => `translate(${this.getXY(source.x0, source.y0)})`)
            .on("click", this.click)
            .on('mouseup', this.mouseup);

        nodeEnter.append("rect")
            .attr('class', 'node-rect')
            .attr("width", width)
            .attr("height", height)
            .attr("stroke", "#999")
            .attr("stroke-width", 2)
            .attr("fill", "#fff")
            .attr("rx", 2)
            .attr("ry", 2);
        nodeEnter.append("rect")
            .attr("class", 'flag')
            .attr("x", 1)
            .attr("y", 1)
            .attr("width", 6)
            .attr("height", height - 2)
            .attr("fill", "#fff")
            .style("fill", (d) => {
                return !d.arrival ? "#f7f7f7" : d.submit ? "#52c41a" : d.over ? '#f5222d' : d.current ? "#2db7f5" : "rgba(45, 183, 245, 0.25)";
            });
        const textg = nodeEnter.append('g').attr('class', 'text-g');
        textg.append("text")
            .attr("x", 20)
            .attr("y", 20)
            .attr("dy", ".35em")
            .attr("text-anchor", textAnchor)
            .text(function (d) {
                return getShortStr(this, isShownameFunc ? showname(d) : d[showname]);
            })
            .on('click', this.clickTitle);
        textg.append('title').text(d => nameTitle ? nameTitle : isShownameFunc ? showname(d) : d[showname]);

        nodeEnter.append("text")
            .attr("display", d => {
                let all = 0;
                if (d.children) {
                    all = d.children.length;
                } else if (d._children) {
                    all = d._children.length;
                }
                return all ? 'block' : 'none';
            })
            .attr("class", 'count')
            .attr("x", width - 30)
            .attr("y", 20)
            .attr("dy", ".35em")
            .attr("text-anchor", textAnchor)
            .text(d => {
                let submitCount = 0;
                let all = 0;
                if (d.children) {
                    submitCount = d.children.filter(d => d.submit).length;
                    all = d.children.length;
                } else if (d._children) {
                    submitCount = d._children.filter(d => d.submit).length;
                    all = d._children.length;
                }
                if (showSubmitCount) {
                    return `${all}/${submitCount}`
                } else {
                    return `${all}`
                }
            });

        const descg = nodeEnter.append('g').attr('class', 'desc-g');
        descg.append("text")
            .attr("class", 'desc')
            .attr("x", 20)
            .attr("y", 40)
            .attr("dy", ".35em")
            .attr("text-anchor", textAnchor).text(function (d) {
                return getShortStr(this, isDescFunc ? desc(d) : d[desc], 120);
            });
        descg.append('title').text(d => isDescFunc ? desc(d) : d[desc]);
        //  descText.append('svg:title').text(d=>d[desc]);
        // Transition nodes to their new position.
        const nodeUpdate = node.transition()
            .duration(duration)
            .attr("transform", d => `translate(${this.getXY(d.x, d.y)})`)
            .attr("class", (d) => {
                if (this.selectedNode && d.id === this.selectedNode.id) {
                    return 'node node-selected'
                } else {
                    return 'node';
                }
            });

        nodeUpdate.select("rect.node-rect")
            .attr("width", width)
            .attr("height", height)
            .attr("stroke", "#999")
            .attr("stroke-width", 2)
            .attr("fill", "#fff");
        nodeUpdate.select("rect.flag")
            .style("fill", (d) => {
                return !d.arrival ? "#f7f7f7" : d.submit ? "#52c41a" : d.over ? '#f5222d' : d.current ? "#2db7f5" : "rgba(45, 183, 245, 0.25)";
            });

        nodeUpdate.select("text").style("fill-opacity", 1);
        nodeUpdate.select("g.text-g>text").text(function (d) {
            return getShortStr(this, isShownameFunc ? showname(d) : d[showname]);
        });
        nodeUpdate.select("g.text-g>title").text(function (d) {
            return nameTitle ? nameTitle : isShownameFunc ? showname(d) : d[showname];
        })
        nodeUpdate.select("g.desc-g>text.desc").text(function (d) {
            return getShortStr(this, isDescFunc ? desc(d) : d[desc], 120);
        });
        nodeUpdate.select("g.desc-g>title").text(function (d) {
            return isDescFunc ? desc(d) : d[desc];
        });
        nodeUpdate.select("g.desc-g>title").text(d => isDescFunc ? desc(d) : d[desc]);

        nodeUpdate.select('text.count').text(d => {
            let submitCount = 0;
            let all = 0;
            if (d.children) {
                submitCount = d.children.filter(d => d.submit).length;
                all = d.children.length;
            } else if (d._children) {
                submitCount = d._children.filter(d => d.submit).length;
                all = d._children.length;
            }
            if (showSubmitCount) {
                return `${all}/${submitCount}`
            } else {
                return `${all}`
            }

        })


        // Transition exiting nodes to the parent's new position.
        const nodeExit = node.exit().transition()
            .duration(duration)
            .attr("transform", d => `translate(${this.getXY(source.x, source.y)})`)
            .remove();

        nodeExit.select("rect")
            .attr("width", width)
            .attr("height", height)
            .attr("stroke", "#ccc")
            .attr("stroke-width", 2);

        nodeExit.select("text");
        // Update the links…
        const link = this.svgGroup.selectAll("path.link")
            .data(links, d => d.target.id)
            .attr('class', "link");

        // Enter any new links at the parent's previous position.
        link.enter().insert("path", "g")
            .attr('class', "link")
            .attr("stroke", d => {
                const { source: { id: from }, target: { id: to } } = d;
                if (this.selectedPath.indexOf(`${from},${to}`) > -1) {
                    return "#2db7f5"
                } else if (this.currentPath.indexOf(`${from},${to}`) > -1) {
                    return "#2db7f5";
                }
                return "#ccc"
            })
            .attr('fill', "none")
            .attr("stroke-width", 1.5)
            .attr("x", width / 2)
            .attr("y", height / 2)
            .attr("d", (d) => {
                const o = {
                    x: source.x0,
                    y: source.y0
                };
                return this.diagonal({
                    source: o,
                    target: o
                });
            });

        // Transition links to their new position.
        link.transition()
            .duration(duration)
            .attr("d", this.diagonal)
            .attr("stroke", d => {
                const { source: { id: from }, target: { id: to } } = d;
                if (this.selectedPath.indexOf(`${from},${to}`) > -1) {
                    return "#2db7f5"
                } else if (this.currentPath.indexOf(`${from},${to}`) > -1) {
                    return "#2db7f5";
                }
                return "#ccc"
            });

        // Transition exiting nodes to the parent's new position.
        link.exit().transition()
            .duration(duration)
            .attr("d", (d) => {
                var o = {
                    x: source.x,
                    y: source.y
                };
                return this.diagonal({
                    source: o,
                    target: o
                });
            })
            .remove();

        // Stash the old positions for transition.
        nodes.forEach((d) => {
            d.x0 = d.x;
            d.y0 = d.y;
        });
    }
    clickTitle = (d) => {
        if (this.props.onClickTitle) {
            this.props.onClickTitle(d);
            window.event.stopPropagation();
        }
    }
    click = (d) => {
        if (d) {
            this.selectedPath = this.getLinksToParents(d);
            this.selectedNode = d;
            // d = this.toggleChildren(d);
            this.update(d);
            if (this.props.onClick) {
                this.props.onClick(d, this.getHasChild(d));
            }
        }
    }
    getLinksToParents = (d) => {
        const selectedPath = [];
        let current = d;
        while (current) {
            selectedPath.push(`${current.parentid},${current.id}`);
            current = current.parent;
        }
        return selectedPath;
    }
    toggleChildren = () => {
        const d = this.opreateNode;
        if (d) {
            if (d.children) {
                d._children = d.children;
                d.children = null;
            } else if (d._children) {
                d.children = d._children;
                d._children = null;
            }
            this.update(d);
        }
    }
    getChildVisible = (d) => {
        return !!d.children;
    }
    getHasChild = (d) => {
        return !!d.children || !!d._children;
    }
}